package com.superbytecode.cloud.dubbo;


import com.superbytecode.cloud.common.exception.BusinessException;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.common.constants.CommonConstants;
import org.apache.dubbo.common.extension.Activate;
import org.apache.dubbo.common.utils.ReflectUtils;
import org.apache.dubbo.common.utils.StringUtils;
import org.apache.dubbo.rpc.*;
import org.apache.dubbo.rpc.service.GenericService;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Objects;

/**
 * All rights Reserved, Designed By www.super-master.com
 *
 * @ProjectName: beacon-cloud
 * @Package: com.superbytecode.cloud.dubbo
 * @ClassName: DubboExceptionFilter
 * @Description: [dubbo自定义异常Filter]
 * <p> </p>
 * @Author: [Lance Ting]
 * @Date: 2024/11/13 17:55
 * @Version: V1.0
 * @Copyright: 2024 www.super-master.com Inc. All rights reserved.
 * TODO: 注意,本文件Lance Ting所作,如果转载或使用请标明具体出处!
 **/
@Slf4j
@Activate(group = {CommonConstants.PROVIDER, CommonConstants.CONSUMER}, order = -1000)
public class DubboExceptionFilter implements Filter, Filter.Listener {

    /**
     * 执行远程服务调用，将请求交给下一个过滤节点。
     *
     * @param invoker    可执行的远程服务接口
     * @param invocation
     * @return
     * @throws RpcException
     */
    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        Result result;
        try {
            // 调用拦截器链或服务提供者
            result = invoker.invoke(invocation);
            Throwable throwable = null;
            if (Objects.isNull(result) || Objects.isNull(throwable = result.getException())) {
                return result;
            }
            // 业务类型异常
            if (throwable.getClass() == BusinessException.class) {
                return result;
            }
            // 封装异常信息
            log.error("dubbo接口异常", result.getException());
            throw new RpcException(handleErrorInfo(invoker, invocation, throwable));
        } catch (RpcException | BusinessException e) {
            throw e;
        } catch (Throwable e) {
            log.error(handleErrorInfo(invoker, invocation, e));
            throw e;
        }
    }

    /**
     * @param appResponse
     * @param invoker
     * @param invocation
     */
    @Override
    public void onResponse(Result appResponse, Invoker<?> invoker, Invocation invocation) {
        // 判断是否包含异常，没有异常直接返回
        if (!appResponse.hasException() || GenericService.class == invoker.getInterface()) {
            return;
        }
        try {
            Throwable throwable = appResponse.getException();

            // 业务异常，直接抛出
            if (throwable instanceof BusinessException) {
                return;
            }

            // directly throw if it's checked exception
            // 非运行时异常且是检查型异常时，直接抛出
            if (!(throwable instanceof RuntimeException) && (throwable instanceof Exception)) {
                return;
            }

            // directly throw if the exception appears in the signature
            // 判断接口声明中是否抛出目标异常，是则直接抛出
            String methodName = invocation.getMethodName();
            Class<?>[] parameterTypes = invocation.getParameterTypes();
            Method method = invoker.getInterface().getMethod(methodName, parameterTypes);
            Class<?>[] exceptionClasses = method.getExceptionTypes();
            for (Class<?> exceptionClass : exceptionClasses) {
                if (throwable.getClass() == exceptionClass) {
                    return;
                }
            }

            // for the exception not found in method's signature, print ERROR message in server's log.
            // 接口声明中没有包含目标异常，打印ERROR日志
            log.error(handleErrorInfo(invoker, invocation, throwable), throwable);

            // directly throw if exception class and interface class are in the same jar file.
            // 如果异常类和接口类型在同一jar中，直接抛出
            String serviceFile = ReflectUtils.getCodeBase(invoker.getInterface());
            String exceptionFile = ReflectUtils.getCodeBase(throwable.getClass());
            if (serviceFile == null || exceptionFile == null || serviceFile.equals(exceptionFile)) {
                return;
            }

            // directly throw if it's JDK exception
            // 根据异常类包名前缀判断是否jdk异常，是则抛出
            String className = throwable.getClass().getName();
            if (className.startsWith("java.") || className.startsWith("javax.")) {
                return;
            }

            // directly throw if it's dubbo exception
            // 判断是否dubbo的rpc异常，是则抛出
            if (throwable instanceof RpcException) {
                return;
            }

            // otherwise, wrap with RuntimeException and throw back to the client
            // 包装成RuntimeException抛出
            appResponse.setException(new RuntimeException(StringUtils.toString(throwable)));
        } catch (Throwable e) {
            log.error("Fail to ExceptionFilter when called by {}. service: {}, method: {}, exception: {}: {}",//
                    RpcContext.getContext().getRemoteHost(), invoker.getInterface().getName(), invocation.getMethodName(), e.getClass().getName(), e.getMessage(), e);
        }
    }

    /**
     * @param t          异常
     * @param invoker
     * @param invocation
     */
    @Override
    public void onError(Throwable t, Invoker<?> invoker, Invocation invocation) {
        log.error("Dubbo exception filter had exception,", t);
    }

    private String handleErrorInfo(Invoker<?> invoker, Invocation invocation, Throwable exception) {
        return "Got unchecked and undeclared exception which called by " + RpcContext.getContext().getRemoteHost()//
                + ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName()//
                + ", exception: " + exception.getClass().getName() + ": " + exception.getMessage()//
                + ", args: " + Arrays.toString(invocation.getArguments());
    }
}